import { toRaw } from 'vue';

!(function () {
	'use strict';

	L.Marker.Measurement = L[L.Layer ? 'Layer' : 'Class'].extend({
		options: {
			pane: 'markerPane',
		},

		initialize: function (latlng, measurement, title, rotation, options) {
			L.setOptions(this, options);

			this._latlng = latlng;
			this._measurement = measurement;
			this._title = title;
			this._rotation = rotation;
		},

		addTo: function (map) {
			map.addLayer(this);
			return this;
		},

		onAdd: function (map) {
			map = toRaw(map);
			this._map = map;
			var pane = this.getPane ? this.getPane() : map.getPanes().markerPane;
			var el = (this._element = L.DomUtil.create('div', 'leaflet-zoom-animated leaflet-measure-path-measurement', pane));
			var inner = L.DomUtil.create('div', '', el);
			inner.title = this._title;
			inner.innerHTML = this._measurement;

			map.on('zoomanim', this._animateZoom, this);

			this._setPosition();
		},

		onRemove: function (map) {
			// map.off('zoomanim', this._animateZoom, this);
			var pane = this.getPane ? this.getPane() : map.getPanes().markerPane;
			pane.removeChild(this._element);
			// this._map = null;
		},

		_setPosition: function () {
			L.DomUtil.setPosition(this._element, this._map.latLngToLayerPoint(this._latlng));
			this._element.style.transform += ' rotate(' + this._rotation + 'rad)';
		},

		_animateZoom: function (opt) {
			if (this._map) {
				var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
				L.DomUtil.setPosition(this._element, pos);
				this._element.style.transform += ' rotate(' + this._rotation + 'rad)';
			}
		},
	});

	L.marker.measurement = function (latLng, measurement, title, rotation, options) {
		return new L.Marker.Measurement(latLng, measurement, title, rotation, options);
	};

	var formatDistance = function (d) {
		var unit, feet;

		if (this._measurementOptions.imperial) {
			feet = d / 0.3048;
			if (feet > 3000) {
				d = d / 1609.344;
				unit = 'mi';
			} else {
				d = feet;
				unit = 'ft';
			}
		} else {
			if (d > 1000) {
				d = d / 1000;
				unit = 'km';
			} else {
				unit = 'm';
			}
		}

		if (d < 100) {
			return d.toFixed(1) + ' ' + unit;
		} else {
			return Math.round(d) + ' ' + unit;
		}
	};

	var formatArea = function (a) {
		var unit, sqfeet;

		if (this._measurementOptions.imperial) {
			if (a > 404.685642) {
				a = a / 4046.85642;
				unit = 'ac';
			} else {
				a = a / 0.09290304;
				unit = 'ft²';
			}
		} else if (this._measurementOptions.ha) {
			if (a > 1000000000) {
				a = a / 1000000000;
				unit = 'km²';
			} else if (a > 10000) {
				a = a / 10000;
				unit = 'ha';
			} else {
				unit = 'm²';
			}
		} else {
			if (a > 1000000) {
				a = a / 1000000;
				unit = 'km²';
			} else {
				unit = 'm²';
			}
		}

		if (a < 100) {
			return a.toFixed(1) + ' ' + unit;
		} else {
			return Math.round(a) + ' ' + unit;
		}
	};

	var RADIUS = 6378137;
	// ringArea function copied from geojson-area
	// (https://github.com/mapbox/geojson-area)
	// This function is distributed under a separate license,
	// see LICENSE.md.
	var ringArea = function ringArea(coords) {
		var rad = function rad(_) {
			return (_ * Math.PI) / 180;
		};
		var p1,
			p2,
			p3,
			lowerIndex,
			middleIndex,
			upperIndex,
			area = 0,
			coordsLength = coords.length;

		if (coordsLength > 2) {
			for (var i = 0; i < coordsLength; i++) {
				if (i === coordsLength - 2) {
					// i = N-2
					lowerIndex = coordsLength - 2;
					middleIndex = coordsLength - 1;
					upperIndex = 0;
				} else if (i === coordsLength - 1) {
					// i = N-1
					lowerIndex = coordsLength - 1;
					middleIndex = 0;
					upperIndex = 1;
				} else {
					// i = 0 to N-3
					lowerIndex = i;
					middleIndex = i + 1;
					upperIndex = i + 2;
				}
				p1 = coords[lowerIndex];
				p2 = coords[middleIndex];
				p3 = coords[upperIndex];
				area += (rad(p3.lng) - rad(p1.lng)) * Math.sin(rad(p2.lat));
			}

			area = (area * RADIUS * RADIUS) / 2;
		}

		return Math.abs(area);
	};
	/**
	 * Handles the init hook for polylines and circles.
	 * Implements the showOnHover functionality if called for.
	 */
	var addInitHook = function () {
		var showOnHover = this.options.measurementOptions && this.options.measurementOptions.showOnHover;
		if (this.options.showMeasurements && !showOnHover) {
			this.showMeasurements();
		}
		if (this.options.showMeasurements && showOnHover) {
			this.on('mouseover', function () {
				this.showMeasurements();
			});
			this.on('mouseout', function () {
				this.hideMeasurements();
			});
		}
	};

	var circleArea = function circleArea(d) {
		var rho = d / RADIUS;
		return 2 * Math.PI * RADIUS * RADIUS * (1 - Math.cos(rho));
	};

	var override = function (method, fn, hookAfter) {
		if (!hookAfter) {
			return function () {
				var originalReturnValue = method.apply(this, arguments);
				var args = Array.prototype.slice.call(arguments);
				args.push(originalReturnValue);
				return fn.apply(this, args);
			};
		} else {
			return function () {
				fn.apply(this, arguments);
				return method.apply(this, arguments);
			};
		}
	};

	L.Polyline.include({
		showMeasurements: function (options) {
			if (!this._map || this._measurementLayer) return this;

			this._measurementOptions = L.extend(
				{
					showOnHover: (options && options.showOnHover) || false,
					minPixelDistance: 30,
					showDistances: true,
					showArea: true,
					showTotalDistance: true,
					lang: {
						totalLength: 'Total length',
						totalArea: 'Total area',
						segmentLength: 'Segment length',
					},
				},
				options || {}
			);

			this._measurementLayer = L.layerGroup().addTo(this._map);
			this.updateMeasurements();

			this._map.on('zoomend', this.updateMeasurements, this);
			this._map.on('moveend', this.updateMeasurements, this);

			return this;
		},

		hideMeasurements: function () {
			if (!this._map) return this;

			this._map.off('zoomend', this.updateMeasurements, this);
			this._map.off('moveend', this.updateMeasurements, this);

			if (!this._measurementLayer) return this;
			this._map.removeLayer(this._measurementLayer);
			this._measurementLayer = null;

			return this;
		},

		onAdd: override(L.Polyline.prototype.onAdd, function (originalReturnValue) {
			var showOnHover = this.options.measurementOptions && this.options.measurementOptions.showOnHover;
			if (this.options.showMeasurements && !showOnHover) {
				this.showMeasurements(this.options.measurementOptions);
			}

			return originalReturnValue;
		}),

		onRemove: override(
			L.Polyline.prototype.onRemove,
			function (originalReturnValue) {
				this.hideMeasurements();

				return originalReturnValue;
			},
			true
		),

		setLatLngs: override(L.Polyline.prototype.setLatLngs, function (originalReturnValue) {
			this.updateMeasurements();

			return originalReturnValue;
		}),

		spliceLatLngs: override(L.Polyline.prototype.spliceLatLngs, function (originalReturnValue) {
			this.updateMeasurements();

			return originalReturnValue;
		}),

		formatDistance: formatDistance,
		formatArea: formatArea,

		updateMeasurements: function () {
			if (!this._measurementLayer) return this;

			var latLngs = this.getLatLngs(),
				isPolygon = this instanceof L.Polygon,
				options = this._measurementOptions,
				totalDist = 0,
				formatter,
				ll1,
				ll2,
				p1,
				p2,
				pixelDist,
				dist;
			console.log(latLngs, latLngs.length, L.Util.isArray(latLngs[0]));
			if (latLngs && latLngs.length && L.Util.isArray(latLngs[0])) {
				// Outer ring is stored as an array in the first element,
				// use that instead.
				latLngs = latLngs[0];
				if (latLngs && latLngs.length && L.Util.isArray(latLngs[0])) {
					latLngs = latLngs[0];
				}
			}

			this._measurementLayer.clearLayers();

			if (this._measurementOptions.showDistances && latLngs.length > 1) {
				formatter = this._measurementOptions.formatDistance || L.bind(this.formatDistance, this);

				for (var i = 1, len = latLngs.length; (isPolygon && i <= len) || i < len; i++) {
					ll1 = latLngs[i - 1];
					ll2 = latLngs[i % len];
					dist = ll1.distanceTo(ll2);
					totalDist += dist;

					p1 = this._map.latLngToLayerPoint(ll1);
					p2 = this._map.latLngToLayerPoint(ll2);

					pixelDist = p1.distanceTo(p2);

					if (pixelDist >= options.minPixelDistance) {
						L.marker
							.measurement(
								this._map.layerPointToLatLng([(p1.x + p2.x) / 2, (p1.y + p2.y) / 2]),
								formatter(dist),
								options.lang.segmentLength,
								this._getRotation(ll1, ll2),
								options
							)
							.addTo(this._measurementLayer);
					}
				}

				// Show total length for polylines
				if (!isPolygon && this._measurementOptions.showTotalDistance) {
					L.marker.measurement(ll2, formatter(totalDist), options.lang.totalLength, 0, options).addTo(this._measurementLayer);
				}
			}

			if (isPolygon && options.showArea && latLngs.length > 2) {
				formatter = options.formatArea || L.bind(this.formatArea, this);
				var area = ringArea(latLngs);
				L.marker.measurement(this.getBounds().getCenter(), formatter(area), options.lang.totalArea, 0, options).addTo(this._measurementLayer);
			}

			return this;
		},

		_getRotation: function (ll1, ll2) {
			var p1 = this._map.project(ll1),
				p2 = this._map.project(ll2);

			return Math.atan((p2.y - p1.y) / (p2.x - p1.x));
		},
	});

	L.Polyline.addInitHook(function () {
		addInitHook.call(this);
	});

	L.Circle.include({
		showMeasurements: function (options) {
			if (!this._map || this._measurementLayer) return this;

			this._measurementOptions = L.extend(
				{
					showOnHover: false,
					showArea: true,
					lang: {
						totalArea: 'Total area',
					},
				},
				options || {}
			);

			this._measurementLayer = L.layerGroup().addTo(this._map);
			this.updateMeasurements();

			this._map.on('zoomend', this.updateMeasurements, this);
			// this._map.on('moveend', this.updateMeasurements, this);

			return this;
		},

		hideMeasurements: function () {
			if (!this._map) return this;

			this._map.on('zoomend', this.updateMeasurements, this);
			// this._map.on('moveend', this.updateMeasurements, this);

			if (!this._measurementLayer) return this;
			this._map.removeLayer(this._measurementLayer);
			this._measurementLayer = null;

			return this;
		},

		onAdd: override(L.Circle.prototype.onAdd, function (originalReturnValue) {
			var showOnHover = this.options.measurementOptions && this.options.measurementOptions.showOnHover;
			if (this.options.showMeasurements && !showOnHover) {
				this.showMeasurements(this.options.measurementOptions);
			}

			return originalReturnValue;
		}),

		onRemove: override(
			L.Circle.prototype.onRemove,
			function (originalReturnValue) {
				this.hideMeasurements();

				return originalReturnValue;
			},
			true
		),

		setLatLng: override(L.Circle.prototype.setLatLng, function (originalReturnValue) {
			this.updateMeasurements();

			return originalReturnValue;
		}),

		setRadius: override(L.Circle.prototype.setRadius, function (originalReturnValue) {
			this.updateMeasurements();

			return originalReturnValue;
		}),

		formatArea: formatArea,

		updateMeasurements: function () {
			if (!this._measurementLayer) return;

			var latLng = this.getLatLng(),
				options = this._measurementOptions,
				formatter = options.formatArea || L.bind(this.formatArea, this);

			this._measurementLayer.clearLayers();

			if (options.showArea) {
				formatter = options.formatArea || L.bind(this.formatArea, this);
				var area = circleArea(this.getRadius());
				L.marker.measurement(latLng, formatter(area), options.lang.totalArea, 0, options).addTo(this._measurementLayer);
			}
		},
	});

	L.Circle.addInitHook(function () {
		addInitHook.call(this);
	});
})();
